package xyz.ck.commons.interval;

import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * </p>
 * 
 * @author admin
 *
 * @param <T>
 */
public class IntervalUnit<T> implements Interval<T> {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2832207068535886386L;

	public static void main(String[] args) throws IntrospectionException {

		IntervalUnit<Integer> a = new IntervalUnit<Integer>(-9, -1);
		IntervalUnit<Integer> b = new IntervalUnit<Integer>(12, 22);

		System.out.println(a.minus(b));

	}

	protected T max = null;

	protected T min = null;

	/**
	 * true ������ false �����䣬Ĭ�ϱ�
	 */

	public IntervalUnit(T min, T max) {
		super();
		if (min == null) {
			throw new IntervalException("min can not be null!");
		}
		if (max == null) {
			throw new IntervalException("max can not be null!");
		}
		if (gt(min, max)) {
			throw new IntervalException("min(" + min
					+ ") can not greater than max(" + max + ")!");
		}
		this.min = min;
		this.max = max;
	}

	public T max(T a, T b) {
		if (this.gteq(a, b)) {
			return a;
		}
		return b;
	}

	public T min(T a, T b) {
		if (this.gteq(a, b)) {
			return b;
		}
		return a;
	}

	/**
	 * core compare method
	 * 
	 * @param a
	 * @param b
	 * @param comparator
	 * @return
	 */
	protected int compare(T a, T b) {

		if (a == null) {
			if (b == null) {
				return 0;
			}
			return 1;
		} else {
			if (b == null) {
				return -1;
			}
			Comparator<? super T> comparator = getComparator();
			if (comparator != null) {
				return comparator.compare(a, b);
			}
			if (a instanceof Comparable) {
				@SuppressWarnings("unchecked")
				Comparable<T> ac = (Comparable<T>) a;
				return ac.compareTo(b);
			}
			throw new IntervalException(
					"You must override getComparator while parameter type is not instanceof Comparable.");
		}

	}

	public boolean eq(T a, T t) {
		int code = compare(a, t);
		return code == 0;
	}

	@Override
	public boolean equals(Object target) {
		if (target == null) {
			return false;
		}
		if (this == target) {
			return true;
		}
		if (target instanceof IntervalUnit) {
			@SuppressWarnings("unchecked")
			IntervalUnit<T> unit = (IntervalUnit<T>) target;
			return this.min.equals(unit.min) && this.max.equals(unit.max);
		}
		return false;
	}

	/**
	 * greater then or equals
	 * 
	 * @param a
	 * @param t
	 * @return
	 */
	public boolean gteq(T a, T t) {
		int code = compare(a, t);
		return code >= 0;
	}

	public Comparator<? super T> getComparator() {
		return null;
	}

	@Override
	public String getStringValue() {
		if (eq(this.min(), this.max())) {
			return this.min().toString();
		}
		return this.min().toString() + "~" + this.max.toString();
	}

	public boolean gt(T a, T t) {
		int code = compare(a, t);
		return code > 0;
	}

	@Override
	public Interval<T> intersect(Interval<T> interval) {
		if (interval.isGroup()) {
			IntervalGroup<T> group = (IntervalGroup<T>) interval;
			return group.intersect(this);
		}

		if (!this.isIntersect(interval)) {
			return null;
		}

		IntervalUnit<T> unit = (IntervalUnit<T>) interval;
		T left = max(this.min(), unit.min());

		T right = min(this.max(), unit.max());
		return this.newInstance(left, right);
	}

	@Override
	public boolean isContains(Interval<T> interval) {
		if (interval.isGroup()) {
			IntervalGroup<T> group = (IntervalGroup<T>) interval;
			return group.isContains(this);
		}
		IntervalUnit<T> unit = (IntervalUnit<T>) interval;
		return this.lteq(this.min(), unit.min())
				&& this.gteq(this.max(), unit.max());

	}

	@Override
	public boolean isContinuous() {
		return false;
	}

	@Override
	public boolean isEmpty() {
		return false;
	}

	@Override
	public boolean isGroup() {
		return false;
	}

	@Override
	public boolean isIn(T t) {
		if (this.lteq(this.min(), t) && this.gteq(this.max(), t)) {
			return true;
		}
		return false;
	}

	protected Interval<T> checkParam(Interval<T> paramInterval) {

		if (paramInterval.isContinuous()) {
			throw new IntervalException(
					"continous can not compute with not continous interval!");
		}
		return paramInterval;

	}

	@Override
	public boolean isIntersect(Interval<T> interval) {
		if (interval.isGroup()) {
			IntervalGroup<T> group = (IntervalGroup<T>) interval;
			return group.isIntersect(this);
		}

		if (gt(this.min(), interval.max())) {
			return false;
		}

		if (lt(this.max(), interval.min())) {
			return false;
		}

		return true;
	}

	@Override
	public boolean isUnit() {
		return true;
	}

	/**
	 * Less then or equals
	 * 
	 * @param a
	 * @param t
	 * @return
	 */
	public boolean lteq(T a, T t) {
		int code = compare(a, t);
		return code <= 0;
	}

	/**
	 * Less then
	 * 
	 * @param a
	 * @param t
	 * @return
	 */
	public boolean lt(T a, T t) {
		int code = compare(a, t);
		return code < 0;
	}

	public T max() {
		return this.max;

	}

	public T min() {
		return this.min;
	}

	public Interval<T> minusUnit(IntervalUnit<T> unit) {
		if (unit == null) {
			return this;
		}
		if (!isIntersect(unit)) {
			return newInstance(this.min(), this.max());
		}
		IntervalUnit<T> target = unit;
		if (target.isContains(this)) {// Ŀ����������˱�����
			return null;
		} else if (this.gt(target.min(), this.min())
				&& this.lt(target.max(), this.max())) {// Ŀ�����䱻��������ȫ�����������佫���ضϳ���������
			IntervalUnit<T> left = newInstance(this.min(),
					target.next(target.min(), false));
			IntervalUnit<T> right = newInstance(
					target.next(target.max(), true), this.max());
			return IntervalGroup.instance(left, right);
		} else if (this.lteq(target.min(), this.min())
				&& this.lt(target.max(), this.max())) {// Ŀ�������뱾������벿���ཻ
			return newInstance(target.next(target.max(), true), this.max());
		} else if (this.gt(target.min(), this.min())
				&& this.gteq(target.max(), this.max())) {// Ŀ�������뱾�����ְ벿���ཻ
			return newInstance(this.min(), this.next(target.min(), false));
		} else {
			throw new IntervalException(this + " minus " + target
					+ " unbelievable case! ");
		}
	}

	public Interval<T> minusGroup(IntervalGroup<T> group) {
		if (group == null || group.isEmpty()) {
			return this;
		}

		List<IntervalUnit<T>> units = new ArrayList<>();

		IntervalUnit<T> source = this;

		for (IntervalUnit<T> targetUnit : group.getUnits()) {
			Interval<T> minusResult = source.minus(targetUnit);
			if (minusResult == null) {
				return null;
			} else if (minusResult.isUnit()) {
				source = (IntervalUnit<T>) minusResult;
			} else if (minusResult.isGroup()) {
				IntervalGroup<T> _group = (IntervalGroup<T>) minusResult;
				if (_group.getUnits().size() == 2) {
					units.add(_group.getUnits().get(0));
					source = _group.getUnits().get(1);
				} else {
					throw new ClassCastException(
							"Separated IntervalUnit should has 2 Items!but "
									+ _group.getUnits().size());
				}
			}

			if (group.getUnits().indexOf(targetUnit) == group.getUnits().size() - 1) {
				units.add(source);
				break;
			}

		}
		if (units == null || units.isEmpty()) {
			return null;
		}
		if (units.size() == 1) {
			return units.get(0);
		}
		return IntervalGroup.instance(units);
	}

	@Override
	public Interval<T> minus(Interval<T> interval) {
		if (interval == null) {
			return this;
		}
		if (interval.isGroup()) {
			return this.minusGroup((IntervalGroup<T>) interval);
		}
		return this.minusUnit((IntervalUnit<T>) interval);
	}

	public IntervalUnit<T> newInstance(T min, T max) {
		return new IntervalUnit<T>(min, max);
	}

	/**
	 * Next value. 2 next is 3 (greator =false) 2 next 1 (greator =true)
	 * 
	 * @param t
	 * @param greater
	 * @return
	 */
	public T next(T t, boolean greater) {
		if (this.isContinuous()) {
			throw new IntervalException();
		}
		if (t instanceof Integer) {
			int i = (Integer) t;
			if (greater) {
				@SuppressWarnings("unchecked")
				T rt = (T) Integer.valueOf(i + 1);
				return rt;
			} else {
				@SuppressWarnings("unchecked")
				T rt = (T) Integer.valueOf(i - 1);
				return rt;
			}
		} else if (t instanceof Long) {
			long i = (Long) t;
			if (greater) {
				@SuppressWarnings("unchecked")
				T rt = (T) Long.valueOf(i + 1L);
				return rt;
			} else {
				@SuppressWarnings("unchecked")
				T rt = (T) Long.valueOf(i - 1L);
				return rt;
			}

		} else if (t instanceof Date) {//
			Date i = (Date) t;
			if (greater) {
				@SuppressWarnings("unchecked")
				T rt = (T) new Date(i.getTime() + 1000L);
				return rt;
			} else {
				@SuppressWarnings("unchecked")
				T rt = (T) new Date(i.getTime() - 1000L);
				return rt;
			}

		} else {
			throw new IntervalException("You have to implement next of "
					+ t.getClass() + "for your own!");
		}
	}

	public boolean rightLeqt(T t) {
		return this.lteq(t, this.max());
	}

	@Override
	public String toString() {
		return this.getStringValue();
	}

	@Override
	public Interval<T> union(Interval<T> interval) {
		if (interval == null) {
			return this;
		}
		if (interval.isGroup()) {
			return interval.union(this);
		}
		IntervalUnit<T> target = (IntervalUnit<T>) interval;
		if (lt(target.max(), this.next(this.min(), false))) {
			
			return IntervalGroup.instance(target, this);
		} else if (gt(target.min(), this.next(this.max(), true))) {
			return IntervalGroup.instance(this, target);
		}
		T minmin = this.min(this.min(), target.min());
		T maxmax = this.max(this.max(), target.max());
		return newInstance(minmin, maxmax);
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	public List<T> listValues(int maxItems) {
		if (this.isContinuous()) {
			throw new IntervalException(
					"Continuous interval can not list values!");
		}
		if (maxItems == 0) {
			maxItems = Integer.MAX_VALUE;
		}
		List<T> list = new ArrayList<T>();
		T current = this.min();
		list.add(current);
		int count = 1;
		while (true) {
			current = this.next(current, true);
			if (count > maxItems && this.gt(current, this.max())) {
				break;
			}
			list.add(current);
			count++;
		}
		return list;
	}

}
